<?

class JCAT_UploadFile
{
	function JCAT_UploadFile($sStoreDir,$sAllowExt=null,$nMaxByte=null)
	{
		// 允许的类型
		if($sAllowExt==null)
		{
			$sAllowExt = self::$ALLOWEXT ;
		}
		$this->SetExts($sAllowExt,true) ;
		
		// 不允许的类型
		$this->SetExts(self::$UNALLOWEXT,false) ;

		$this->SetMaxByte($nMaxByte) ;
		$this->SetStoreDir($sStoreDir) ;
	}
	
	## 属性 -----------------
	
	/**
	 * 错误代号常量
	 * 
	 * @access	const
	 * @var		int
	 */
	const ERR_NOUPLOAD = 1 ;								// 文件未上传
	const ERR_EXMAXBYTE_BY_PHP = 2 ;						// 超过限制字节数（在 php.ini 中设定）
	const ERR_UNALLOWEXT = 3 ;								// 不允许的文件类型
	const ERR_EXMAXBYTE = 4 ;								// 超过限制字节数
	const ERR_STOREDIR_NOTEXIST = 5 ;						// 存储目录不存在，且 JCAT_UploadFile::SetAutoCreateStoreDir(false) ;
	const ERR_STOREDIR_UNCREATE = 6 ;						// 无法自动创建 存储目录
	const ERR_UNCOPY = 7 ;									// 无法将上传文件 由 临时路径拷贝至 存储路径
	const ERR_CANCEL = 8 ;									// 上传被取消
	
	static public $MINBYTE = 0 ;								// 允许上传的 最小字节数
	static public $MAXBYTE = 204800 ;							// 允许上传的 最大字节数， 204800byte = 200Kb
	static public $ALLOWEXT = array('*') ;					// 允许上传的 文件扩展名
	static public $UNALLOWEXT = array(						// 禁止上传的 文件扩展名
				'php', 'php5' 									// ... ...
			) ;
	static public $PATH_STOREFILE ;
	
	
	protected $sLastInput ;
	
	protected $arrStorePath = array() ;						// 存储路径
	
	protected $arrStoreSubPath = array() ;					// 存储子目录+存储文件名
	
	protected $arrStoreFileName = array() ;					// 存储文件名
	
	protected $arrErrors = array() ;
	
	protected $nMaxByte ;										// 允许上传的
	
	protected $arrUnallowExt = array() ;						// 不允许的 文件类型
	protected $arrAllowExt = array() ;							// 允许的 文件类型
	
	protected $bKeepOriginalName = false ;						// 保持原名
	
	protected $bOverlayIfExisted = true ;						// 同名覆盖
	
	protected $sStoreDir ;										// 存储目录
	protected $sStoreSubDir ;									// 存储子目录
	
	protected $callbackGenerateNewName = null ;				// 用于产生新文件名的 回调函数
	
	protected $bAutoCreateStoreDir = true ;					// 自动创建存储目录
	
	protected $nRandStrLen = 4 ;
	
	

	## 方法 --------------
	
	public function Upload($sInputName)
	{
		$this->sLastInput = $sInputName ;

		if( !isset($_FILES[$sInputName]) or empty($_FILES[$sInputName]['name']) )
		{
			$this->_PutErrorInfo($sInputName,"文件“{$sInputName}”未上传",self::ERR_NOUPLOAD) ;
			return false ;
		}
		
		if( empty($_FILES[$sInputName]['tmp_name']) )
		{
			$this->_PutErrorInfo($sInputName,"获得文件“{$_FILES[$sInputName]['name']}”信息，但文件未上传至服务器，可能文件超过服务器的长度限制（参考 php.ini 的相关设定值）",self::ERR_EXMAXBYTE_BY_PHP) ;
			return false ;			
		}
		
		// 检查类型
		$sExtName = $this->GetOriginalExt($sInputName) ;
		if( !$this->IsAllowExt($sExtName) )
		{
			$this->_PutErrorInfo($sInputName,"上传的文件类型：“{$sExtName}”受限制",self::ERR_UNALLOWEXT) ;
			return false ;
		}

		// 检查字节
		$nByte = $this->GetLength($sInputName) ;
		if( $this->nMaxByte>0 && $this->nMaxByte<$nByte )
		{
			$this->_PutErrorInfo($sInputName,'上传的文件字节长度（'.JCAT_UploadFile::GetReadableFileSize($nByte).'）超过限制（'.JCAT_UploadFile::GetReadableFileSize($this->nMaxByte).'）',self::ERR_EXMAXBYTE) ;
			return false ;			
		}
		
		// 确定 文件名
		$sStoreName = $this->MakeStoreName($sInputName) ;
		$sStorePath = $this->sStoreDir.$sStoreName ;
		$sStoreRealDir = dirname($sStorePath) ;
		if( !is_dir($sStoreRealDir) )
		{
			if(!$this->bAutoCreateStoreDir)
			{
				$this->_PutErrorInfo($sInputName,"存储目录不存在：“{$sStoreRealDir}”",self::ERR_STOREDIR_NOTEXIST) ;
				return false ;
			}
			else if( !mkdir($sStoreRealDir,0777,true) )
			{
				$this->_PutErrorInfo($sInputName,"无法创建存储目录：“{$sStoreRealDir}”",self::ERR_STOREDIR_UNCREATE) ;
				return false ;
			}
		}

		$this->arrStoreSubPath[$sInputName] = $sStoreName ;
		$this->arrStorePath[$sInputName] = $sStorePath ;
		
		// 移动文件
		$sTempPath = $this->GetTempPath($sInputName) ;
		if( !move_uploaded_file($sTempPath,JCAT::CharsetToServer($sStorePath)) or !is_file($sStorePath) )
		{
			$this->_PutErrorInfo($sInputName,"将上传文件从临时路径：“{$sTempPath}”拷贝至存储路径：“{$sStorePath}”时发生了错误",self::ERR_UNCOPY) ;
			return false ;			
		}
		
		return true ;
	}

	public function CancelUpload($sInputName=null)
	{
		if($sInputName===null)
		{
			$sInputName = $this->sLastInput ;
		}

		if( is_file($this->GetStorePath()) )
		{
			unlink($this->GetStorePath()) ;
		}

		unset($this->arrStoreFileName[$sInputName]) ;
		unset($this->arrStorePath[$sInputName]) ;
		unset($this->arrStoreSubPath[$sInputName]) ;

		$this->_PutErrorInfo($sInputName,"文件上传被取消",self::ERR_CANCEL) ;
	}
	
	public function GetStorePath($sInputName=null)
	{ return $this->arrStorePath[($sInputName==null)?$this->sLastInput:$sInputName] ; }
	
	public function GetStoreSubPath($sInputName=null)
	{ return $this->arrStoreSubPath[($sInputName==null)?$this->sLastInput:$sInputName] ; }
	
	public function GetStoreFileName($sInputName=null)
	{ return $this->arrStoreFileName[($sInputName==null)?$this->sLastInput:$sInputName] ; }
	
	
	## -- 新文件名 -- ##
	public function MakeStoreName($sInputName=null)
	{
		if($sInputName==null)
		{
			$sInputName = $this->sLastInput ;
		}
		
		// 通过设定的回调函数 得到新文件名
		if($this->callbackGenerateNewName!==null)
		{
			return call_user_func_array($this->callbackGenerateNewName,array(&$this,$sInputName)) ;
		}
		
		// 保持 原有文件名
		if($this->bKeepOriginalName)
		{
			return $this->sStoreSubDir.$this->GetOriginalName($sInputName) ;
		}
		
		// 产生随机文件名
		$NewName = time().'.' ;
		$box = array_merge(range('a','z'),range('A','Z'),range(0,9)) ;
		shuffle($box) ;
		$RandLen = $this->nRandStrLen ;
		while( $RandLen-- > 0)
		{
			$NewName.= $box[ array_rand($box) ] ;
		}
		
		$this->arrStoreFileName[$sInputName] = $NewName.'-'.str_replace('/','-63-',base64_encode($this->GetOriginalName($sInputName))) ;
		return $this->sStoreSubDir.$this->arrStoreFileName[$sInputName] ;
	}
	
	public function SetNewNameGenerater($sFunctionName=null,$mixedClassOrOb=null)
	{
		if($mixedClassOrOb===null)
		{
			$this->callbackGenerateNewName = $sFunctionName ;
		}
		else
		{
			$this->callbackGenerateNewName = array($mixedClassOrOb,$sFunctionName) ;
		}
	}
	
	/**
	 * 从默认的存储文件名中 取得原始文件名
	 *
	 * @access	public
	 * @param	$sStoreName		string
	 * @static
	 * @return	string
	 */
	static public function GetOriginalNameFromStoreName($sStoreName)
	{
		$arrResult = array() ;
		if( preg_match('/^\d{10}\.\w+\-([\-\w\.+=]+)$/',$sStoreName,$arrResult) )
		{
			return base64_decode(str_replace('-63-','/',$arrResult[1])) ;
		}
		
		else
		{
			return $sStoreName ;
		}
	}
	
	/**
	 * 检查是否上传
	 *
	 * @access	public
	 * @param	$sInputName
	 * @return	bool
	 */
	public function IsUploading($sInputName)
	{
		return !empty($_FILES[$sInputName]['tmp_name']) ;
	}
	
	## -- 存储目录 -- ##
	public function SetStoreDir($sStoreDir)
	{
		$this->sStoreDir = JCAT_Global::TidyPath($sStoreDir) ;
	}
	public function SetStoreSubDir($sStoreSubDir)
	{
		$this->sStoreSubDir =  JCAT_Global::TidyPath($sStoreSubDir) ;
	}
	public function GetStoreDir()
	{ return $this->sStoreDir ; }
	public function GetStoreSubDir()
	{ return $this->sStoreSubDir ; }
	
	
	## -- 原始文件信息 -- ##
	
	public function GetOriginalName($sInputName=null)
	{ return $_FILES[($sInputName==null)?$this->sLastInput:$sInputName]['name'] ; }
	
	public function GetOriginalExt($sInputName=null)
	{ return $this->GetExtName( $this->GetOriginalName($sInputName) ) ; }
	
	public function GetOriginalType($sInputName=null)
	{ return $_FILES[($sInputName==null)?$this->sLastInput:$sInputName]['type'] ; }
	
	public function GetTempPath($sInputName=null)
	{ return $_FILES[($sInputName==null)?$this->sLastInput:$sInputName]['tmp_name'] ; }
	
	public function GetLength($sInputName=null)
	{ return $_FILES[($sInputName==null)?$this->sLastInput:$sInputName]['size'] ; }
	
	
	
	## --- 错误信息 --- ##
	
	protected function _PutErrorInfo($sInputName,$Msg,$Code)
	{
		$this->arrErrors[$sInputName] = array(
			'Msg' => $Msg ,
			'Code' => $Code ,
		) ;
	}
	
	public function GetErrorMsg($sInputName=null,$sLanguage=null)
	{
		if($sInputName==null)
		{
			$sInputName = $this->sLastInput ;
		}
		
		$sMsg = isset($this->arrErrors[$sInputName]['Msg'])?
					$this->arrErrors[$sInputName]['Msg']:
					"未知的 Input Name {$sInputName}" ;
		
		return JCAT_Language::SentenceEx($sMsg,'JCAT',$sLanguage) ; 
	}
	
	public function GetErrorCode($sInputName=null)
	{ return $this->arrErrors[($sInputName==null)?$this->sLastInput:$sInputName]['Code'] ; }
	
	
	
	## --- 扩展名/文件类型 --- ##
	static function GetExtName($sFileName)
	{
		$arr = explode('.',$sFileName) ;
		$FileExtName = $arr[ count($arr)-1 ] ;
		return strtolower($FileExtName) ;
	}
	public function SetExts($mixedExtName,$bAllow=true)
	{
		if( $bAllow )
		{
			$arr =& $this->arrAllowExt ;
		}
		else
		{
			$arr =& $this->arrUnallowExt ;
		}
		$mixedExtName = (array) $mixedExtName ;
		foreach ($mixedExtName as $sExtName)
		{
			$arr[$sExtName] = self::GetExtName($sExtName) ;
		}
	}
	public function RemoveExt($sExtName,$bAllow=true)
	{
		$arr =& $bAllow? $this->arrAllowExt: $this->arrUnallowExt ;
		$sExtName = JCAT_UploadFile::GetExtName($sExtName) ;
		unset($arr[$sExtName]) ;
	}
	public function GetExts($bAllow=true)
	{
		return $bAllow? $this->arrAllowExt: $this->arrUnallowExt ;
	}
	public function IsAllowExt($sExtName)
	{
		$sExtName = JCAT_UploadFile::GetExtName($sExtName) ;
		if( isset($this->arrUnallowExt[$sExtName]) )
		{
			return false ;
		}
		if( isset($this->arrAllowExt['*']) )
		{
			return true ;
		}
		return isset($this->arrAllowExt[$sExtName]) ;
	}
	
	
	
	## --- 行为 --- ##
	
	public function KeepOriginalName($bKeepOriginalName=true,$bOverlayIfExisted=false)
	{
		$this->bKeepOriginalName = $bKeepOriginalName ;
		$this->bOverlayIfExisted = $bOverlayIfExisted ;
	}
	
	public function SetOverlayIfExisted($bOverlayIfExisted=false)
	{
		$old = $this->bOverlayIfExisted ;
		$this->bOverlayIfExisted = $bOverlayIfExisted ;
		return $old ;		
	}
	
	public function SetAutoCreateStoreDir($bAutoCreateStoreDir=true)
	{
		$old = $this->bAutoCreateStoreDir ;
		$this->bAutoCreateStoreDir = $bAutoCreateStoreDir ;
		return $old ;		
	}
	
	public function SetMaxByte($nMaxByte=null)
	{
		$old = $this->nMaxByte ;
		
		if($nMaxByte==null)
		{
			$nMaxByte = self::$MAXBYTE ;
		}
		$this->nMaxByte = $nMaxByte ;

		return $old ;
	}
	
	
	## --- ... --- ##
	static function GetReadableFileSize($nByte,$nPrecision=2)
	{
		$unit = 'Byte' ;
		if( $nByte>=1024 )
		{
			$nByte/= 1024 ;
			$unit = 'KB' ;
		}
		if( $nByte>=1048576 )
		{
			$nByte/= 1024 ;
			$unit = 'MB' ;
		}
		if( $nByte>=1073741824 )
		{
			$nByte/= 1024 ;
			$unit = 'GB' ;
		}
		
		return round($nByte,$nPrecision).' '.$unit ;
	}
}

?>